package io.sundial.application.impl;

import io.sundial.core.context.Clazz;
import io.sundial.core.context.Context;
import io.sundial.core.context.RoleException;
import io.sundial.core.context.Supplier;
import io.sundial.core.context.exception.RoleNotFoundException;
import io.sundial.core.context.exception.RoleNotSuppliedException;
import io.sundial.core.context.exception.RoleNotUniqueException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

/**
 * 框架上下文
 *
 * @author Payne 646742615@qq.com
 * 2018/12/22 21:19
 */
public class StandaloneContext implements Context {
    private final DefaultListableBeanFactory beanFactory;

    StandaloneContext(String... configLocations) {
        configLocations = configLocations != null && configLocations.length > 0 ? configLocations : new String[]{"sundial.xml"};
        this.beanFactory = new DefaultListableBeanFactory();

        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.setValidating(false);
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(String name) throws RoleException, ClassCastException {
        try {
            return (T) beanFactory.getBean(name);
        } catch (NoSuchBeanDefinitionException e) {
            throw new RoleNotFoundException(e);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(String name, Supplier<T> supplier) throws RoleException, ClassCastException {
        try {
            return (T) beanFactory.getBean(name);
        } catch (NoSuchBeanDefinitionException e) {
            synchronized (beanFactory) {
                try {
                    return (T) beanFactory.getBean(name);
                } catch (NoSuchBeanDefinitionException ex) {
                    T supplied = supplier != null ? supplier.supply(this) : null;
                    if (supplied == null) {
                        throw new RoleNotSuppliedException(ex);
                    }
                    beanFactory.registerSingleton(name, supplied);
                    return supplied;
                } catch (Exception ex) {
                    throw new RoleException(ex);
                }
            }
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> T get(Class<T> type) throws RoleException {
        try {
            return beanFactory.getBean(type);
        } catch (NoUniqueBeanDefinitionException e) {
            throw new RoleNotUniqueException(e);
        } catch (NoSuchBeanDefinitionException e) {
            throw new RoleNotFoundException(e);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> T get(Class<T> type, Supplier<T> supplier) throws RoleException {
        try {
            return beanFactory.getBean(type);
        } catch (NoUniqueBeanDefinitionException e) {
            throw new RoleNotUniqueException(e);
        } catch (NoSuchBeanDefinitionException e) {
            synchronized (beanFactory) {
                try {
                    return beanFactory.getBean(type);
                } catch (NoUniqueBeanDefinitionException ex) {
                    throw new RoleNotUniqueException(ex);
                } catch (NoSuchBeanDefinitionException ex) {
                    T supplied = supplier != null ? supplier.supply(this) : null;
                    if (supplied == null) {
                        throw new RoleNotSuppliedException(ex);
                    }
                    beanFactory.registerSingleton(type.getName(), supplied);
                    return supplied;
                } catch (Exception ex) {
                    throw new RoleException(ex);
                }
            }
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> T get(Clazz<T> type, Supplier<T> supplier) throws RoleException {
        try {
            return beanFactory.getBean(type.getRawClass());
        } catch (NoUniqueBeanDefinitionException e) {
            throw new RoleNotUniqueException(e);
        } catch (NoSuchBeanDefinitionException e) {
            synchronized (beanFactory) {
                try {
                    return beanFactory.getBean(type.getRawClass());
                } catch (NoUniqueBeanDefinitionException ex) {
                    throw new RoleNotUniqueException(ex);
                } catch (NoSuchBeanDefinitionException ex) {
                    T supplied = supplier != null ? supplier.supply(this) : null;
                    if (supplied == null) {
                        throw new RoleNotSuppliedException(ex);
                    }
                    beanFactory.registerSingleton(type.getRawClass().getName(), supplied);
                    return supplied;
                } catch (Exception ex) {
                    throw new RoleException(ex);
                }
            }
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public Class<?> check(String name) throws RoleException {
        try {
            return beanFactory.getType(name);
        } catch (NoSuchBeanDefinitionException e) {
            throw new RoleNotFoundException(e);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> Map<String, T> fetch(Class<T> type) throws RoleException {
        try {
            return beanFactory.getBeansOfType(type);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public Iterator<String> iterator() {
        return Arrays.asList(beanFactory.getBeanDefinitionNames()).iterator();
    }
}
